<?php
    /**
     * TCPPC API
     * api.php
     * Date:2024/07/31
     * @version 2.3.02
     * @author 布丁小才 zyn_games_email@yeah.net
     */


    /**
     * @version 2.3.02
     * 更新内容：
     * 重构接入层API（api.php）
     * 增加安全防火墙（firewall.php）
     * 统一接口规范
     */

    //是否为命令行执行
    if (!php_sapi_name() === 'cli') {  
        echo '警告：请使用终端或命令提示符（cmd）运行此脚本';
        exit;
    }

    if (!file_exists(__DIR__.'\firewall.php')) {
        e('防火墙文件不存在！');
        exit;
    }

    require_once __DIR__.'\firewall.php';

    //从network.php中调用API的类
    class TCPPC_SERVER_API {
        /**
         * WS服务器路径
         * @var string
         */
        public $websocket_path = '/ws_server';
        /**
         * 操作类
         * @var operate
         */
        private $operate;

        /**
         * 初始化
         */
        public function __construct() {
            $this->operate = new operate();
        }

        /**
         * Websocket新用户连接时执行的操作
         * @param websocket_send $api
         * @return void
         */
        public function websocket_welcome(websocket_send $api) {
            $this->operate->run('welcome',array(),$api,'ws',$api->k);
        }

        /**
         * WebSocket新消息接收
         * @param string $msg
         * @param websocket_send $api
         * @return void
         */
        public function websocket(string $msg,websocket_send $api) {
            $data = json_decode($msg,true);
            if ($data == '' or $data == false) {
                e("错误: 用户".$api->k."发出了一个错误的JSON请求");
                $api->send(json_encode(array(
                    "code" => 400,
                    "type" => 'api',
                    "data" => array(
                        "msg" => 'JSON格式错误'
                    )
                ),JSON_UNESCAPED_UNICODE));
                return;
            }

            if ($data["type"] == 'close') {
                $api->send(json_encode(array(
                    "code" => 200,
                    "type" => "server",
                    "data" => array(
                        "msg" => "Bye"
                    )
                ),JSON_UNESCAPED_UNICODE));
                $api->close();
                return;
            }
            if ($data["type"] == 'ping') {
                $api->send(json_encode(array(
                    "code" => 200,
                    "type" => "server",
                    "data" => array(
                        "msg" => "Hello"
                    )
                ),JSON_UNESCAPED_UNICODE));
                return;
            }
            
            //分离接入点和参数
            $access = (isset($data["type"]) and is_string($data["type"])) ? $data["type"] : '';
            $parameter = (isset($data["data"]) and is_array(($data["data"]))) ? $data["data"] : array();
            $this->operate->run($access,$parameter,$api,'ws',$api->k);
        }

        /**
         * HTTP服务端数据接收
         * @param string $path
         * @param array $http_data
         * @param http_api $api
         * @return void
         */
        public function http(string $path,array $http_data,http_api $api) {
            //ID
            if (isset($http_data['cookie']['tcppc_server_user_id'])) {
                $k = $http_data['cookie']['tcppc_server_user_id'];
            } else {
                $k = uniqid();
            }

            //POST请求路径
            if ($path == '/api/post2') {
                if ($http_data["server"]["REQUEST_METHOD"] == 'POST') {
                    $data = $http_data["post"];
                    foreach ($data as $key => $value) {
                        if ($key == 'type') {
                            $access = $value;
                        } else {
                            $parameter[$key] = $value;
                        }
                    }
                    $this->operate->run(
                        ((isset($access) and is_string($access)) ? $access : ''),
                        ((isset($parameter) and is_array($parameter)) ? $parameter : array()),
                        $api,
                        'http',
                        $k
                    );
                    return;
                } else {
                    $api->return(405,json_encode(array(
                        "code" => 405,
                        "type" => 'api',
                        "data" => array(
                            "msg" => '请使用POST方式请求此API'
                        )
                    ),JSON_UNESCAPED_UNICODE));
                    return;
                }
            }

            //GET请求路径
            $get_pos = strpos($path,'/api/query') + strlen('/api/query');
            if ($get_pos !== false) {  
                $get_result = substr($path,$get_pos);  
                if (substr($get_result,0,1) === '/') {  
                    $get_result = substr($get_result,1);  
                }

                $this->operate->run($get_result,$http_data["get"],$api,'http',$k);
                return;
            }

            //找不到API的错误处理
            $api->return(404,json_encode(array(
                "code" => 404,
                "type" => 'api',
                "data" => array(
                    "msg" => '找不到API'
                )
            ),JSON_UNESCAPED_UNICODE));
            return;
        }
    }

    //执行操作层API
    class operate {
        /**
         * Root密码
         * @var string
         */
        public $root_pwd = null;
        /**
         * Server密码
         * @var string
         */
        public $server_pwd = null;
        /**
         * 已认证用户存储
         * @var array
         */
        public $user = array();

        /**
         * 防火墙类
         * @var firewall
         */
        private $firewall;

        /**
         * 二级验证密码
         * @var string
         */

        private $server2_pwd;

        /**
         * 初始化Root密码和ServerLogin密码
         */
        public function __construct() {
            $this->root_pwd = strval(mt_rand(100000,999999).time().mt_rand(100000,999999));
            e('Server_Root密码：'.$this->root_pwd);
            if (file_exists('data\connect.pwd')) {
                $this->server_pwd = preg_replace('/\R+/','',file_get_contents('data\connect.pwd'));
            } else {
                $this->server_pwd = '';
            }

            if (file_exists('data\auth2.pwd')) {
                $this->server2_pwd = preg_replace('/\R+/','',file_get_contents('data\auth2.pwd'));
            } else {
                e('必须设置二级密码(auth2.pwd)');
                exit;
            }

            $this->firewall = new firewall();
        }

        /**
         * 判断执行路径并执行对应操作
         * @param string $access
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */

        public function run(string $access = '',array $parameter = array(),$api,string $mode,string $k) {
            //认证操作
            if ($access !== 'connect' and $access !== 'welcome') {
                if (!isset($this->user[$k])) {
                    $this->send(array(
                        "code" => 401,
                        "type" => "auth",
                        "data" => array(
                            "msg" => '身份无效'
                        )
                    ),401,$api,$mode,false);
                    return;
                }
    
                if ($this->user[$k]["to_time"] < time()) {
                    $this->send(array(
                        "code" => 403,
                        "type" => "auth",
                        "data" => array(
                            "msg" => '身份凭证过期'
                        )
                    ),403,$api,$mode,false);
                    unset($this->user[$k]);
                    return;
                }
            }

            //执行判断
            switch ($access) {
                case 'connect':
                    $this->connect($parameter,$api,$mode,$k);
                    break;
                case 'welcome':
                    $this->send(array(
                        "code" => 200,
                        "type" => "welcome",
                        "data" => array(
                            "msg" => "Welcome To TCPPC WebSocket Server",
                            "initialize_auth" => array(
                                "had_server_pwd" => (($this->server_pwd != null) ? 'true' : 'false')
                            ),
                            "server_type" => array(
                                "computer_name" => gethostname(),
                                "server_protocol" => "TCPPC",
                                "version" => TCPPC_SERVER_VERSION
                            )
                        )
                    ),200,$api,$mode,false);
                    break;
                case 'server_root':
                    $this->server_root($parameter,$api,$mode,$k);
                    break;
                case 'shutdown':
                    $this->shutdown($parameter,$api,$mode,$k);
                    break;
                case 'reboot':
                    $this->reboot($parameter,$api,$mode,$k);
                    break;
                case 'locked':
                    $this->locked($api,$mode,$k);
                    break;
                case 'run_app':
                    $this->run_app($parameter,$api,$mode,$k);
                    break;
                case 'kill_app':
                    $this->kill_app($parameter,$api,$mode,$k);
                    break;
                default:
                    //错误处理
                    $this->send(array(
                        "code" => 404,
                        "type" => 'api',
                        "data" => array(
                            "msg" => '找不到API'
                        )
                    ),404,$api,$mode,false);
                    break;
            }
        }

        /**
         * 授权
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function connect(array $parameter,$api,string $mode,string $k) {
            if (($this->server_pwd != null) ? true : false) {
                if ($parameter["server_pwd"] == md5($this->server_pwd)) {
                    $user_time = time()+601;
                    $this->user[$k] = array(
                        "to_time" => $user_time,
                        "key" => $k
                    );

                    $this->send(array(
                        "code" => 200,
                        "type" => "connect",
                        "data" => array(
                            "msg" => "OK"
                        )
                    ),200,$api,$mode,true,$k,$user_time);

                    e("用户".$k."通过验证");
                    return;
                } else {
                    $this->send(array(
                        "code" => 403,
                        "type" => "connect",
                        "data" => array(
                            "msg" => "密码错误"
                        )
                    ),403,$api,$mode,false);
                    return;
                }
            } else {
                $user_time = time()+601;
                $this->user[$k] = array(
                    "to_time" => $user_time,
                    "key" => $k
                );

                $this->send(array(
                    "code" => 200,
                    "type" => "connect",
                    "data" => array(
                        "msg" => "OK"
                    )
                ),200,$api,$mode,true,$k,$user_time);

                e("用户".$k."通过验证");
                return;
            }
        }

        /**
         * 操作server_root
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function server_root(array $parameter,$api,string $mode,string $k) {
            if ($parameter["server_root_pwd"] == $this->root_pwd) {
                if ($parameter["server_command"] == 'quit_server') {
                    $this->send(array(
                        "code" => 200,
                        "type" => "server_root",
                        "data" => array(
                            "msg" => "服务器已关闭"
                        )
                    ),200,$api,$mode,false);

                    if ($mode == 'ws') {
                        $api->close_to_all();
                    }

                    e("Server_Root：用户".$k."关闭了服务器");
                    exit;
                } else {
                    $this->send(array(
                        "code" => 404,
                        "type" => "server_root",
                        "data" => array(
                            "msg" => "指令不受支持"
                        )
                    ),404,$api,$mode,false);
                }
            }
        }

        /**
         * 关机操作
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function shutdown(array $parameter,$api,string $mode,string $k) {
            if (isset($parameter["wait"]) and is_bool($parameter["wait"]) and $parameter["wait"] == true) {
                if (isset($parameter["wait_time"]) and is_int($parameter["wait_time"])) {
                    $this->send(array(
                        "code" => 200,
                        "type" => "shutdown",
                        "data" => array(
                            "msg" => "OK"
                        )
                    ),200,$api,$mode,false);
                    e('用户'.$k.'执行了'.$parameter["wait_time"].'秒后关机');
                    exec('call shutdown /s /t '.$parameter["wait_time"]);
                    return;
                }
            }

            if (isset($parameter["close"]) and is_bool($parameter["close"]) and $parameter["close"] == true) {
                $this->send(array(
                    "code" => 200,
                    "type" => "shutdown",
                    "data" => array(
                        "msg" => "OK",
                        "close" => true
                    )
                ),200,$api,$mode,false);
                exec('call shutdown /a');
                e('用户'.$k.'取消了关机或重启');
                return;
            }

            exec('call shutdown /s');
            $this->send(array(
                "code" => 200,
                "type" => "shutdown",
                "data" => array(
                    "msg" => "OK"
                )
            ),200,$api,$mode,false);
            e('用户'.$k.'执行了关机');
            return;
        }

        /**
         * 重启操作
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function reboot(array $parameter,$api,string $mode,string $k) {
            if (isset($parameter["wait"]) and is_bool($parameter["wait"]) and $parameter["wait"] == true) {
                if (isset($parameter["wait_time"]) and is_int($parameter["wait_time"])) {
                    $this->send(array(
                        "code" => 200,
                        "type" => "reboot",
                        "data" => array(
                            "msg" => "OK"
                        )
                    ),200,$api,$mode,false);
                    e('用户'.$k.'执行了'.$parameter["wait_time"].'秒后重启');
                    exec('call shutdown /r /t '.$parameter["wait_time"]);
                    return;
                }
            }

            exec('call shutdown /r');
            $this->send(array(
                "code" => 200,
                "type" => "reboot",
                "data" => array(
                    "msg" => "OK"
                )
            ),200,$api,$mode,false);
            e('用户'.$k.'执行了重启');
            return;
        }

        /**
         * 锁定操作
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function locked($api,string $mode,string $k) {
            $this->send(array(
                "code" => 200,
                "type" => "locked",
                "data" => array(
                    "msg" => "OK"
                )
            ),200,$api,$mode,false);
            e('用户'.$k.'执行了锁定');
            exec('call rundll32.exe user32.dll,LockWorkStation');
            return;
        }

        /**
         * 运行一个软件
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        private function run_app(array $parameter,$api,string $mode,string $k) {
            if (!isset($parameter["app_path"]) or !is_string($parameter["app_path"])) {
                $this->send(array(
                    "code" => 404,
                    "type" => "run_app",
                    "data" => array(
                        "msg" => "缺失参数：运行程序路径/名"
                    )
                ),404,$api,$mode,false);
                return;
            }

            $parameter["print_data"] = (isset($parameter["print_data"]) and strtolower($parameter["print_data"]) == 'true' or $parameter["print_data"] == true) ? true : false;

            if (!file_exists($parameter["app_path"])) {
                $this->send(array(
                    "code" => 404,
                    "type" => "run_app",
                    "data" => array(
                        "msg" => "程序不存在"
                    )
                ),404,$api,$mode,false);
                return;
            }

            //危险内容被拦截
            if (!$this->firewall->dos_shell($parameter["app_path"].((isset($parameter["parameter"]) and is_string($parameter["parameter"])) ? ' '.$parameter["parameter"] : ''))) {
                if (isset($parameter["auth2_pwd"]) and is_string($parameter["auth2_pwd"])) {
                    if (!(md5($parameter["auth2_pwd"]) == $this->server2_pwd)) {
                        $this->send(array(
                            "code" => 403,
                            "type" => "run_app",
                            "data" => array(
                                "msg" => "安全密码错误"
                            )
                        ),403,$api,$mode,false);
                        return;
                    }
                } else {
                    e('用户'.$k.'尝试执行危险程序，被拦截，程序路径：'.$parameter["app_path"]);
                    $this->send(array(
                        "code" => 403,
                        "type" => "run_app",
                        "data" => array(
                            "msg" => "此命令存在安全隐患，必须输入安全密码才可执行"
                        )
                    ),403,$api,$mode,false);
                    return;
                }
            }

            //正常执行
            $app_parameter = array();
            e('用户'.$k.'执行了'.$parameter["app_path"]);
            exec('start '.$parameter["app_path"].((isset($parameter["parameter"]) and is_string($parameter["parameter"])) ? ' '.$parameter["parameter"] : ''),$app_parameter);
            if ($parameter["print_data"]) {
                $parameter_row = '';
                foreach ($app_parameter as $parameter_rows) {  
                    $parameter_row .= $parameter_rows.PHP_EOL;
                } 

                $this->send(array(
                    "code" => 200,
                    "type" => "run_app",
                    "data" => array(
                        "msg" => "OK",
                        "data" => base64_encode(iconv('gbk','utf-8//IGNORE',$parameter_row))
                    )
                ),200,$api,$mode,false);
            } else {
                $this->send(array(
                    "code" => 200,
                    "type" => "run_app",
                    "data" => array(
                        "msg" => "OK"
                    )
                ),200,$api,$mode,false);
            }
            return;
        }

        /**
         * 结束程序
         * @param array $parameter
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param string $k
         * @return void
         */
        public function kill_app(array $parameter,$api,string $mode,string $k) {
            $kill_mode = '';
    
            if (isset($parameter["kcp"]) and $parameter["kcp"]) {
                $kill_mode .= ' /t';
            }

            if (isset($parameter["force"]) and $parameter["force"]) {
                $kill_mode .= ' /f';
            }

            if (!$this->firewall->kill_task((isset($parameter["app_name"]) ? $parameter["app_name"] : $parameter["app_pid"]))) {
                e('用户'.$k.'尝试结束关键系统程序，被拦截');
                $this->send(array(
                    "code" => 403,
                    "type" => "kill_app",
                    "data" => array(
                        "msg" => "你尝试结束关键系统程序，被拦截"
                    )
                ),403,$api,$mode,false);
                return;
            }

            if (isset($parameter["app_name"])) {
                exec('call taskkill'.$kill_mode.' /im '.$parameter["app_name"].' 2>&1',$error,$code);
            } else if (isset($parameter["app_pid"])) {
                exec('call taskkill'.$kill_mode.' /pid '.$parameter["app_pid"].' 2>&1',$error,$code);
            } else {
                $this->send(array(
                    "code" => 404,
                    "type" => "kill_app",
                    "data" => array(
                        "msg" => "缺失参数：程序名称或PID",
                    )
                ),404,$api,$mode,false);
                return;
            }

            if ($code == 0) {
                e('用户'.$k.'结束了'.(isset($parameter["app_name"]) ? $parameter["app_name"] : $parameter["app_pid"]));
                $this->send(array(
                    "code" => 200,
                    "type" => "kill_api",
                    "data" => array(
                        "msg" => "OK"
                    )
                ),200,$api,$mode,false);
            } else {
                foreach ($error as $error_rows) {  
                    $error_row .= $error_rows.PHP_EOL;
                }  

                $this->send(array(
                    "code" => 501,
                    "type" => "kill_api",
                    "data" => array(
                        "msg" => iconv('gbk','utf-8//IGNORE',$error_row)
                    )
                ),501,$api,$mode,false);
            }

            return;
        }


        /**
         * 通过API向HTTP或WS发送消息
         * @param array $msg
         * @param int $http_code
         * @param websocket_send|http_api $api
         * @param string $mode
         * @param bool $login_ok
         * @param string $k
         * @param int $guoqi_time
         * @return void
         */

        public function send(array $msg,int $http_code,$api,string $mode,bool $login_ok = false,string $k = '',int $guoqi_time = 0) {
            switch ($mode) {
                case 'ws':
                    $api->send(json_encode($msg,JSON_UNESCAPED_UNICODE));
                    break;
                case 'http':
                    if ($login_ok == true) {
                        $api->setcookie('tcppc_server_user_id',$k,$guoqi_time,'/api');
                    }

                    $api->return($http_code,json_encode($msg,JSON_UNESCAPED_UNICODE));
                    break;
            }
        }
    }